這題給定 n 和 k 的情況,返回 n! 個排列的第 k 個排列,因為排列數的成長很快速,如果逐一生成所有排列再找到第 k 個,效率會超低,所以需要一個更好的方式,直接算出第 k 個排列。
想法:
理解排列結構,每個位置的數字選擇受之前選擇的影響,例如:假設有 3 個數字 [1, 2, 3],總共有 6 種排列。固定第一個數字:
1 開頭,有 2! = 2 種排列:[123, 132]
2 開頭,也有 2 種排列:[213, 231]
3 開頭,有 2 種排列:[312, 321]
這表示,可以根據 k 的值,推出第一個數字是什麼,然後把問題縮小到剩下的數字,用階乘來算位置,可以透過 k // (n-1)! 來確定第 k 個排列的第一個數字,再用 k % (n-1)! 繼續算下一個位置,遞歸或迭代解法,對每個位置,重複上面的步驟,直到找到第 k 個排列。
技巧:
階乘,理解 n! 是排列數的基本構成,用這個來分割排列數組,縮小問題範圍,每次選一個數字後,把問題縮小到 n-1 個數字上,k 的更新,每次選完一個數後,用 k % (n-1)! 更新 k,就能找到剩下部分的第 k 個排列。
思路:
建一個從 1 到 n 的數字列表,用 k // (n-1)! 找到每個位置應該放哪個數字,再用更新後的 k 繼續計算下一個位置的數字,最後把這些數字拼起來,構成第 k 個排列。
程式碼:
import math
class Solution(object):
def getPermutation(self, n, k):
"""
:type n: int
:type k: int
:rtype: str
"""
#建 1 到 n 的數字列表
numbers = list(range(1, n+1))
#轉換 k 為 0-indexed
k -= 1
result = []
#算每一層的數字
for i in range(n, 0, -1):
# 算階乘 (i-1)!
fact = math.factorial(i-1)
#決定當前位置的數字
index = k // fact
result.append(str(numbers[index]))
#從數字列表裡刪已用過的數字
numbers.pop(index)
#更新 k 值
k %= fact
return ''.join(result)
解釋:
初始化數字列表,numbers = list(range(1, n+1)),從這個列表裡每次選擇數字,調整 k,k -= 1 是為了把 k 變成 0 索引,比較好算,用迴圈算每個位置的數字,fact = math.factorial(i-1) 算階乘,找到當前應選的數字位置 index = k // fact,把這個數字加到結果,再從 numbers 刪調那個數字,更新 k,每次用 k %= fact 更新 k,讓剩下的數字位置繼續計算。
問題:
索引錯誤,要注意 k 是從 1 開始不是 0,所以開始要對讓k 減 1。
效率:雖然用階乘來減少排列生成的數量,但如果沒有處理好數組的選擇和更新,還是會出現效率低下的情況。
這個時間複雜度是 O(n^2),因為每次選擇數字後從 numbers 刪除數字是線性操作。